// Copyright (C) 2008 Taylor L. Riche <riche@cs.utexas.edu>
//  
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software 
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//

package syscomp.entities;

import java.net.URI;
import java.io.*;
import java.util.ArrayList;

import core.CodeGenerator;
import core.Entity;


public class ProcessingElement extends Entity{
	private final String init;
	private final String destroy;
	private final String type;
	private final String core;
	private final Timer time;
	private final ArrayList<String> srcChanList;
	private final ArrayList<String> dstChanList;
	private final ArrayList<String> memList;
	private final String procMtx;
	
	/**
	 * @param init
	 * @param destroy
	 * @param type
	 */
	public ProcessingElement(final String name, final URI fileURI, final String init, final String destroy, final String type,
			final String core, final Timer time, final ArrayList<String> scl, final ArrayList<String> dcl, final ArrayList<String> ml, final String procMtx) {
		this.setName(name);
		this.setFileURI(fileURI);
		this.init = init;
		this.destroy = destroy;
		this.type = type;
		this.time = time;
		this.srcChanList = scl;
		this.core = core;
		this.dstChanList = dcl;
		this.memList = ml;
		this.procMtx = procMtx;
	}
	public static ProcessingElement create (String n, URI f, String i, String d, String t, String c, Timer tm, ArrayList<String> scl, ArrayList<String> dcl, ArrayList<String> ml, String pmtx) {
		return new ProcessingElement(n, f, i, d, t, c, tm, scl, dcl, ml, pmtx);
	}
	/**
	 * @return the destroy
	 */
	public String getDestroy() {
		return destroy;
	}
	/**
	 * @return the init
	 */
	public String getInit() {
		return init;
	}
	/**
	 * @return the type
	 */
	public String getType() {
		return type;
	}
	/**
	 * @return the core
	 */
	public String getCore() {
		return core;
	}
	public ArrayList<String> getSrcChanList() {
		return srcChanList;
	}
	public ArrayList<String> getDstChanList() {
		return dstChanList;
	}
	/**
	 * @return the procMtx
	 */
	public String getProcMtx() {
		return procMtx;
	}
	public void generateIncludes(PrintStream file) {
		file.println("#include \"lc_" + this.getName() + "_ProcElement.h\"");
	}
	public void generateGhostIncludes(PrintStream file) {
		file.println("#include \"lc_" + this.getName() + "_GhostProcElement.h\"");
	}
	/* (non-Javadoc)
	 * @see syscomp.entities.Entity#generateCode()
	 */
	@Override
	public void generateCode(CodeGenerator cg) {
		
		// Generate ghost processor file
		
		String filename = "lc_" + this.getName() + "_GhostProcElement.h";
		FileOutputStream out = null;
		PrintStream file = null;
		try {
		    out = new FileOutputStream(filename);
		    file = new PrintStream(out);
		}
		catch (FileNotFoundException e) {
		    System.err.println("Specified file " + filename + " does not exist.");
		    System.exit(1);
		}

		// Print guards
		file.println("#ifndef LC_" + this.getName().toUpperCase() + "_H_");
		file.println("#define LC_" + this.getName().toUpperCase() + "_H_");
		file.println();

		file.println("#include <lagniappe/ProcElement.h>");

		// Start local includes
		// First, the source channels for this proc
		for(String chan : srcChanList) {
			file.println("#include \"lc_" + chan + "_CommChannel.h\"");
		}
		// Now, all sink channels
		for(String chan : dstChanList) {
			file.println("#include \"lc_" + chan + "_CommChannel.h\"");
		}
		
		// Start class definition
		file.println();
		file.println("class " + this.getName() + "Ghost : public lagniappe::ProcElement {");
		
		// private data section
		file.println("private:");
		file.println("\tvirtual lagniappe::RCon * getRCon() {assert(false);}");
		file.println();

		// public methods
		file.println("public:");
		file.println("\t" + this.getName() + "Ghost(bool f = false, std::string n = \"\") : lagniappe::ProcElement(f, n) {}");
		file.println("\tvirtual ~" + this.getName() + "Ghost() {this->destroy();}");
		//file.println("\tvoid setSrcChan(lagniappe::CommChannel * sc) {sourceChannel = sc;}");
		file.println("\tvoid addSrcChan(lagniappe::CommChannel * sc) {sourceChannels.push_back(sc); chanIter = sourceChannels.begin();}");
		file.println("\tvoid addDestChan(lagniappe::ProcElement * p, lagniappe::CommChannel * dc) {chanMap[p] =  dc;}");
		file.println();
		file.println("\tvirtual void processRequest(lagniappe::RCon * rc) {}");
		file.println("\tvirtual void init() {}");
		file.println("\tvirtual void destroy() {}");
		file.println("\tvirtual bool isOverloaded(lagniappe::RCon * rc) {return false;}");
		file.println("\tvirtual void unFreeze() {}");
		file.println("\tvirtual void getThread() {}");
		file.println("\tvirtual void startTimer() {}");
		file.println("\tvirtual double endTimer() {}");
		file.println("\tvirtual void profileWait() {}");
		file.println("\tvirtual void profileNotify() {}");
		file.println("\tvirtual void profileLock() {}");
		file.println("\tvirtual void profileUnlock() {}");
		file.println("\tvirtual bool isLocal() {return false;}");
		
		file.println("};");
		
		file.println("#endif");
		
		// Generate real processor file
		
		filename = "lc_" + this.getName() + "_ProcElement.h";
		out = null;
		file = null;
		try {
		    out = new FileOutputStream(filename);
		    file = new PrintStream(out);
		}
		catch (FileNotFoundException e) {
		    System.err.println("Specified file " + filename + " does not exist.");
		    System.exit(1);
		}

		// Print guards
		file.println("#ifndef LC_" + this.getName().toUpperCase() + "_H_");
		file.println("#define LC_" + this.getName().toUpperCase() + "_H_");
		file.println();
		
		Mutex mtx = (Mutex)cg.lookupName(this.procMtx);		

		// Print standard header info
		file.println("#include <assert.h>");
		file.println("#include <map>");
		file.println("#include <lagniappe/ProcElement.h>");
		file.println("#include \"" + mtx.getFileURI().toString() + "\"");
		file.println("#include \"" + time.getFileLocation().toString() + "\"");
		file.println("#include <iostream>");
		file.println();
		
		// Start local includes
		// First, the source channels for this proc
		for(String chan : srcChanList) {
			file.println("#include \"lc_" + chan + "_CommChannel.h\"");
		}
		// Now, all sink channels
		for(String chan : dstChanList) {
			file.println("#include \"lc_" + chan + "_CommChannel.h\"");
		}
		// Next, the memory resources this proc can access
		for(String mem : memList) {
			file.println("#include \"lc_" + mem + "_Memory.h\"");
		}
		
		file.println("#include \"" + this.getFileURI() + "\"");
		file.println();
		
		// Start class definition
		file.println();
		file.println("class " + this.getName() + " : public lagniappe::ProcElement, " + mtx.getCore() + " {");
		
		// private data section
		file.println("private:");
		
		// instantiate member variables
		
		// now support multiple incoming channels
		//file.println("\tlagniappe::CommChannel * sourceChannel;");
		
		// Instance of the core type
		file.println("\t" + this.core + " coreThread;");
		
		// Instance of the timer
		file.println("\t" + this.time.getCoreType() + " timer;");
		
		// private method
		//file.println("\tvirtual lagniappe::RCon * getRCon() {assert(sourceChannel != NULL); return sourceChannel->get();}");
		file.println("\tvirtual lagniappe::RCon * getRCon() {lagniappe::CommChannel * retChan = NULL; do{retChan = *chanIter; chanIter++; if(chanIter == sourceChannels.end()) chanIter = sourceChannels.begin();} while (retChan->empty()); return retChan->get();}");
		file.println();

		// public methods
		file.println("public:");
		file.println("\t" + this.getName() + "(bool f = false, std::string n = \"\") : lagniappe::ProcElement(f, n) {std::cerr << name << std::endl;}");
		file.println("\tvirtual ~" + this.getName() + "() {this->destroy();}");
		//file.println("\tvoid setSrcChan(lagniappe::CommChannel * sc) {sourceChannel = sc;}");
		file.println("\tvoid addSrcChan(lagniappe::CommChannel * sc) {sourceChannels.push_back(sc); chanIter = sourceChannels.begin();}");
		file.println("\tvoid addDestChan(lagniappe::ProcElement * p, lagniappe::CommChannel * dc) {chanMap[p] =  dc;}");
		file.println();
		//file.println("\tvirtual void processRequest(lagniappe::RCon * rc) {assert(sourceChannel != NULL); assert(rc != NULL); sourceChannel->put(rc);}");
		file.println("\tvirtual void processRequest(lagniappe::RCon * rc) {}");
		file.println("\tvirtual void init() {coreThread." + this.init + "(lagniappe::startProc, (void *)this);}");
		file.println("\tvirtual void destroy() {coreThread." + this.destroy + "();}");
		//file.println("\tvirtual bool isOverloaded(lagniappe::RCon * rc) {if(freeze) {return false;} else if(sourceChannel->getCount() > rc->getOp()->getQueueThreshold()) {return true;} else {return false;}}");
		file.println("\tvirtual bool isOverloaded(lagniappe::RCon * rc) {return false;}");
		file.println("\tvirtual void unFreeze() {lock(); freeze = false; unlock();}");
		file.println("\tvirtual void getThread() {std::cout << coreThread.getThread();}");
		file.println("\tvirtual void startTimer() {timer." + time.getStart() + "();}");
		file.println("\tvirtual double endTimer() {return timer." + time.getStop() + "();}");
		file.println("\tvirtual void profileWait() {wait();}");
		file.println("\tvirtual void profileNotify() {notify();}");
		file.println("\tvirtual void profileLock() {lock();}");
		file.println("\tvirtual void profileUnlock() {unlock();}");
		file.println("\tvirtual bool isLocal() {return true;}");
		
		// Close class definition
		file.println();
		file.println("};");
		
		// Close guards
		file.println("#endif");
		file.println();
		
	}
	/**
	 * @return the time
	 */
	public Timer getTime() {
		return time;
	}
}
